import java.net.*;
import java.io.*;
import java.util.*;

public class IterativeChatServer implements ApplicationListener{ 
    //The server has a record as well, as it will also communicate on the broadcast channel
    //to inform clients of added/removed clients
    private ChatClientRecord serverRec;
    private ArrayList<ChatClientRecord> groupList;
    private ReliableChannel rc;

    private class RegFailureException extends Exception { }

    //cleans up timed-out clients every second
    private class ClientWatchdog extends Thread {
        public void run() {
            System.out.println("Client watchdog started");
            // Cleanup old clients
            while (true) {
                synchronized (groupList) {
                    for(int i = 0; i < groupList.size(); i++){
                        ChatClientRecord rec = groupList.get(i);
                        long currentTime = System.currentTimeMillis();
                        if (currentTime - rec.lastHeartbeat > 60*1000) {
                            System.out.println("Removing idle client " + rec.name);
                            groupList.remove(i); 
                            rc.removeMember(rec);
                            rc.sendMessage(new ChatMessage(ChatMessage.GET_LIST, null, groupList, serverRec));
                        }
                    }
                }
                try{ Thread.sleep(1000); } catch(InterruptedException e){ System.exit(1); }
            }
        }
    }

    public IterativeChatServer(int serverPort) {
        // Keep a record of available clients
        groupList = new ArrayList<ChatClientRecord>();
        // Create the server's chat record
        try{
            //the server has 2 ports its listening on, the specified for traditional connections and 1+specified for broadcasts
            serverRec = new ChatClientRecord("__server",InetAddress.getLocalHost().getHostName(),serverPort+1);
            serverRec.lastHeartbeat = Long.MAX_VALUE;//make it so the server's 'client' can never time-out
            groupList.add(serverRec);
        }catch(UnknownHostException e){
            System.err.println("Error: "+e.getMessage());
            System.exit(1);
        }

        // Start up server
        ServerSocket serverSocket = null;
        try {
            serverSocket = new ServerSocket(serverPort);
            System.out.println("IterativeChatServer started on port " + serverPort);
        } catch (IOException e) {
            System.err.println("Could not listen on port: " + serverPort);
            System.exit(1);
        }

        // Start idle client cleanup
        ClientWatchdog watch = new ClientWatchdog();
        watch.start();

        //start up the reliable channel
        rc = new ReliableChannel(serverRec, this);

        // Accept chat client connections
        while (true) {
            Socket clientSocket = null;
            try {
                // Accept a new connection
                clientSocket = serverSocket.accept();
                System.out.println("Accepted connection from " 
                        + clientSocket.getInetAddress().getHostName() 
                        + ":" + clientSocket.getPort());
                //get streams
                ObjectInputStream in = new ObjectInputStream(clientSocket.getInputStream());
                ObjectOutputStream out = new ObjectOutputStream(clientSocket.getOutputStream());
                //read message
                ChatMessage request = (ChatMessage)in.readObject();

                if (request.type == ChatMessage.REG_ATTEMPT) {
                    // Attempt to register client
                    try {
                        synchronized (groupList) {
                            for (ChatClientRecord record : groupList)
                                if (record.equals(request.clientRec))
                                    throw new RegFailureException();
                            request.clientRec.lastHeartbeat = System.currentTimeMillis();
                            groupList.add(request.clientRec);
                            rc.addMember(request.clientRec);
                            ChatMessage msg = new ChatMessage(ChatMessage.REG_SUCCESS, null, groupList, null);
                            out.writeObject(msg);
                        }
                    } catch (RegFailureException e) {
                        System.out.println("A client with name " + request.clientRec.name + " already exists.");
                        ChatMessage msg = new ChatMessage(ChatMessage.REG_FAIL, null, null, null);
                        out.writeObject(msg);
                    }
                } else if (request.type == ChatMessage.HEARTBEAT) {
                    // Update last heartbeat time to current time
                    ChatClientRecord clientRec = request.clientRec;
                    String name = clientRec.name;
                    synchronized (groupList) {
                        for (ChatClientRecord rec : groupList)
                            if (rec.name.equals(name))
                                rec.lastHeartbeat = System.currentTimeMillis();
                    }
                } else if (request.type == ChatMessage.GET_LIST) {
                    // Send group list back to client
                    synchronized (groupList) {
                        ChatMessage msg = new ChatMessage(ChatMessage.GET_LIST, null, groupList, request.clientRec);
                        out.writeObject(msg);
                    }
                    System.out.println("Sent list to client");
                } else if (request.type == ChatMessage.REG_LEAVE) {
                    synchronized(groupList) {
                        groupList.remove(request.clientRec);
                        rc.removeMember(request.clientRec);
                        rc.sendMessage(new ChatMessage(ChatMessage.GET_LIST, null, groupList, this.serverRec));
                    }
                }
                //Note: Because the server is 'registered' in the chat group
                //it receives all the chat IM's but it just ignores them

                //close connection
                clientSocket.close();
            } catch (ClassNotFoundException e) {
                System.err.println("Could not find class ChatMessage");
                System.exit(1);
            } catch (IOException e) {
                System.err.println("Accept failed (IOException)");
            }
        }
    }

    public void receive(ChatMessage m){
    }

    public static void main(String[] args) {
        int serverPort;
        if (args.length == 1) {
            serverPort = Integer.parseInt(args[0]);
            new IterativeChatServer(serverPort);
        }else{
            System.err.println("Usage: java IterativeServer <serverPort>");
            System.exit(1);
        }
    }
}
